
#include "WriteProcess.hpp"
#include "datakit.h"
#include <iostream>
#include "tess/tess.h"
#include "common/logger.h"

namespace  AMCAX::Exchange {

    WriteProcessBase::WriteProcessBase(Dtk_string inputFile, std::unique_ptr<WriterBase> writer)
        : m_inputFile(inputFile), m_fileWriter(std::move(writer))
    {
        
    }
    
    Dtk_ErrorStatus WriteProcessBase::WriteDocument(Dtk_MainDocPtr inDocument, const std::string& outputDir)
    {
        return dtkNoError;
    }
    
    Dtk_ErrorStatus WriteProcessBase::WriteInstance(Dtk_ComponentPtr inComponent)
    {
        return dtkNoError;
    }

    Dtk_ErrorStatus WriteProcessBase::WriteComponent(Dtk_ComponentPtr inComponent, const Dtk_transfo &inMatrix, bool bCreateFile)
    {
        return dtkNoError;        
    }
    
    Dtk_ErrorStatus WriteProcessBase::WritePrototype(Dtk_ComponentPtr inComponent, const Dtk_transfo &inMatrix, Dtk_ID& outPrototypeId)
    {
        return dtkNoError;
    }
    
    void WriteProcessBase::WriteMetaData(Dtk_NodePtr inNode)
    {
        // subtype can be DTK_TYPE_PUBLICATION, DTK_TYPE_MEASURE or DTK_TYPE_METADATA
        Dtk_MetaDataPtr TmpMetaData = inNode->GetDtk_MetaDataPtr();
        if (TmpMetaData.IsNotNULL())
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteMetaData(TmpMetaData);
            }
        }
    }
    void WriteProcessBase::WriteDrawing(Dtk_NodePtr inNode)
    {

        Dtk_DrawingPtr TmpDrawing = inNode->GetDtk_DrawingPtr();
        if (TmpDrawing.IsNotNULL())
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteDrawing(TmpDrawing);
            }
    
        }
    }

    
    void WriteProcessBase::WriteDtk_Mesh(Dtk_MeshPtr inMeshToWrite, Dtk_tab<Dtk_ID>& outIds)
    {
        // convert triangles-strip, fans, polygons into simple triangles
        inMeshToWrite->explode();

        if (m_fileWriter)
        {
            m_fileWriter->WriteMesh(inMeshToWrite, outIds);
        }

        Dtk_RGB rgb = inMeshToWrite->get_mesh_color();   // mesh global color.

        // vertices :
        Dtk_Size_t nbvertices = inMeshToWrite->get_nb_vertices();
        Dtk_Size_t i, j;
        for (i = 0; i < nbvertices; i++)
        {
            Dtk_pnt pnt;
            Dtk_dir dir;
            Dtk_RGB color;
            float u, v;
            u = v = 0.0;
            inMeshToWrite->get_vertex(i, &pnt);  //  coordinates of the vertices
            if (inMeshToWrite->has_normals())
                inMeshToWrite->get_normal(i, &dir);  // vertex normal (if enabled)
            if (inMeshToWrite->has_colors())
                inMeshToWrite->get_color(i, &color);  // vertex color (if enabled)
            if (inMeshToWrite->has_texcoords())
            {
                u = inMeshToWrite->GetUTexture(i);  // vertex u,v texture coordinate (if enabled)
                v = inMeshToWrite->GetVTexture(i);
            }
        }
        // mesh_faces
        Dtk_Size_t k, m, nbmeshfaces = inMeshToWrite->get_nb_mesh_face();
        for (i = 0; i < nbmeshfaces; i++)
        {
            Dtk_mesh_face* mf = inMeshToWrite->get_mesh_face(i);
            Dtk_Size_t nbtriangles = mf->get_nbtriangles();
            Dtk_Int32 trianglesindices[3];
            for (j = 0; j < nbtriangles; j++)
            {
                const Dtk_UInt32* tri = mf->get_triangle_indices(j);
                trianglesindices[0] = tri[0];
                trianglesindices[1] = tri[1];
                trianglesindices[2] = tri[2];
                // id of vertex in vertices table you read in the first step of this function
            }

            //face boundaries
            Dtk_Size_t nbpolyline = mf->get_nbpolylines();
            for (j = 0; j < nbpolyline; j++)
            {
                const Dtk_tab<Dtk_UInt32> *pnt;
                pnt = mf->get_polyline_indices(j);
                Dtk_Size_t nbvertex = pnt->size();
                for (k = 0; k < nbvertex; k++)
                {
                    m = (*pnt)[k];  // id of vertex in vertices table
                    Dtk_pnt pt = inMeshToWrite->get_vertex(m);
                }
            }
        }
    }

    void WriteProcessBase::WriteMesh(Dtk_NodePtr inNode, Dtk_tab<Dtk_ID>& outIds)
    {
        Dtk_MeshPtr TmpMesh = inNode->GetDtk_MeshPtr();
        if (TmpMesh.IsNotNULL())
        {
            Dtk_transfo currentMatrix;
            TmpMesh->Transform(currentMatrix);
            WriteDtk_Mesh(TmpMesh, outIds);
        }
    }

    void WriteProcessBase::WriteKinematics(Dtk_NodePtr inNode)
    {
        Dtk_KinematicPtr TmpKinematics = inNode->GetDtk_KinematicPtr();
        if (TmpKinematics.IsNotNULL())
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteKinematics(TmpKinematics);
            }
    
        }
    }
    void WriteProcessBase::WriteBody(Dtk_NodePtr inNode, Dtk_tab<Dtk_ID>& outIds)
    {
        // Get if the node represent infinite geometry
        // 1 = Infinite, 0 = Finite
        int NodeInfiniteGeometry = inNode->GetInfos()->GetInfiniteGeometryFlag();
        if (NodeInfiniteGeometry == 0)
        {
            //Calling both methods GetDtk_MeshPtr() and GetDtk_BodyPtr() on BodyNode will give you the same result
            //Choose the one you need in order to avoid duplicated data
            Dtk_BodyPtr TmpBody = inNode->GetDtk_BodyPtr();
            if (TmpBody.IsNotNULL())
            {
                if (m_fileWriter)
                {
                    m_fileWriter->WriteBody(TmpBody, outIds);
                }
            }

            // Some CAD formats store also faceted data besides B-Rep. 
            // So you can get faceted data corresponding to the body using the following method
            const Dtk_MeshPtr TmpFacetedBody = inNode->GetDtk_MeshPtr();
            if (TmpFacetedBody.IsNotNULL())
            {
                TmpFacetedBody->Transform(m_currentMatrix);
                WriteDtk_Mesh(TmpFacetedBody, outIds);
            }

            // If there is no mesh data associated to the current body, you can tessellate (and if init of tessellation engine is successful)
            if (TmpFacetedBody.IsNULL() && TmpBody.IsNotNULL() && GetTesselationStatus() == dtkNoError)
            {
                Dtk_tab<Dtk_MeshPtr> meshes;
                Dtk_tab<Dtk_Int32> isclosed;
                int err_tess = tess_BodyToMeshes(TmpBody, meshes, isclosed);
                if( inNode->Name().len() > 0 )
                    std::cout << "NodeName = " << inNode->Name().c_str() << ";";
                std::cout << "err_tess = " << dtkTypeError( err_tess ).c_str() << std::endl ;
                if (err_tess == 0)
                {
                    Dtk_Size_t i, nbmeshes = meshes.size();
                    for (i = 0; i < nbmeshes; i++)
                    {
                        meshes[i]->Transform(m_currentMatrix);
                        WriteDtk_Mesh(meshes[i], outIds);
                    }
                }
            }
        }
    }
    void WriteProcessBase::WriteFdt(Dtk_NodePtr inNode)
    {
        enum type_detk subtype;
        subtype = inNode->GetNodeSubType();
        // subtype can be DTK_TYPE_MEASURE or DTK_TYPE_FDT
    
        Dtk_FdtPtr TmpFdt = inNode->GetDtk_FdtPtr();
        if (TmpFdt.IsNotNULL())
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteFdt(TmpFdt);
            }
        }
    }
    void WriteProcessBase::WriteFeature(Dtk_NodePtr inNode)
    {
        // Get the Feature associated to the current node, if exists,
        // otherwise a NULL pointer is returned
        Dtk_FeaturePtr TmpFeature = inNode->GetDtk_FeaturePtr();
        if (TmpFeature.IsNotNULL())
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteFeature(TmpFeature);
            }

        }
    }
    void WriteProcessBase::WriteAxisSystem(Dtk_NodePtr inNode)
    {
        enum type_detk subtype;
        subtype = inNode->GetNodeSubType();
        // subtype can be DTK_TYPE_VIEW or DTK_TYPE_AXIS_SYSTEM
    
        Dtk_AxisSystemPtr TmpAxis = inNode->GetDtk_AxisSystemPtr();
        if (TmpAxis.IsNotNULL() )
        {
            Dtk_string SystemName = TmpAxis->get_info()->GetName();
    
            if (m_fileWriter)
            {
                m_fileWriter->WriteAxisSystem(TmpAxis);
            }
        }
    }

    void WriteProcessBase::WriteLayerFilter(Dtk_NodePtr inNode)
    {
        Dtk_LayerInfosSetPtr TmpLayerInfosSet = inNode->GetDtk_LayerInfosSetPtr();
        if (TmpLayerInfosSet.IsNotNULL())
        {
            Dtk_Size_t NumLayers = TmpLayerInfosSet->GetNumLayers();
            Dtk_Size_t NumLayerFilters = TmpLayerInfosSet->GetNumLayerFilters();
            Dtk_Size_t DefaultLayer, DefaultLayerFilter;
            TmpLayerInfosSet->GetDefaultLayer(DefaultLayer);
            TmpLayerInfosSet->GetDefaultLayerFilter(DefaultLayerFilter);
    
            Dtk_Size_t i;
            for (i = 0; i < NumLayers; i++)
            {
                Dtk_string LayerName;
                TmpLayerInfosSet->GetLayerName(i, LayerName);
            }
            for (i = 0; i < NumLayerFilters; i++)
            {
                Dtk_LayerFilterInfosPtr TmpLayerFilter = TmpLayerInfosSet->GetLayerFilterByPos(i);
                Dtk_tab< Dtk_Size_t > SelectedLayers;
                TmpLayerFilter->GetSelectedLayers(SelectedLayers);
                Dtk_string LayerFilterName;
                TmpLayerFilter->GetName(LayerFilterName);
            }
            if (m_fileWriter)
            {
                m_fileWriter->WriteLayerFilter(TmpLayerInfosSet);
            }
        }
    }
    void WriteProcessBase::WriteCamera(Dtk_NodePtr inNode)
    {
        Dtk_CameraPtr TmpCamera = inNode->GetDtk_CameraPtr();
        if (TmpCamera.IsNotNULL())
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteCamera(TmpCamera);
            }
        }
    }
    void WriteProcessBase::WriteModelDisplay(Dtk_NodePtr inNode)
    {
        // check is it used ??
    }

    void WriteProcessBase::WriteConstraints(Dtk_NodePtr inNode)
    {
        Dtk_ConstraintPtr TmpConstraint = inNode->GetDtk_ConstraintPtr();
        if( TmpConstraint.IsNotNULL() )
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteConstraint(TmpConstraint);
            }
        }
    }

    void WriteProcessBase::WriteColorPalette(Dtk_NodePtr inNode)
    {
        Dtk_ColorInfosSetPtr TmpColorInfos = inNode->GetDtk_ColorInfosSetPtr();
        if (TmpColorInfos.IsNotNULL())
        {
            if (m_fileWriter)
            {
                m_fileWriter->WriteColorPalette(TmpColorInfos);
            }
        }
    }

    Dtk_ErrorStatus WriteProcessBase::WriteNode(Dtk_NodePtr inNode, Dtk_tab<Dtk_ID>& outIds)
    {
        //GetName
        Dtk_string nodeName = inNode->Name();
        
        if (m_fileWriter)
        {
            m_fileWriter->InitNode(inNode);  
        }
    
        //Get The node Blanked Status
        // -1 = undefined, 0 = Visible, 1=Invisible, 2=Construction Geometry
        int nodeBlankedStatus = inNode->GetInfos()->GetBlankedStatus();
    
        Dtk_Node::NodeDataTypeEnum nodeType = inNode->GetNodeType();
    
        //In this sample we read and treat only visible node - change this test if you want to read invisible and/or construction node
        if( nodeBlankedStatus == 0 )
        {
            //You have many types for Node Data : 
            // BodyType for 3D geometry (solid and wireframe)
            // MeshType for 3D tessellated geometry
            // FdtType for FDT
            // DrawingType for 2D
            // KinematicsType for Kinematics
            // AxisSystemType for AxisPlacement
            // LayerInfosSetType for LayerInfos
            // MetaDataType for Additional informations
            // CameraType for Camera 
            // ModelDisplayType for ModelDisplay
            // VirtualType just for containing children
            switch( nodeType )
            {
                case Dtk_Node::BodyType:
                {
                    WriteBody( inNode, outIds );
                    break;
                }
                case Dtk_Node::FdtType:
                {
                    WriteFdt( inNode );
                    break;
                }
                case Dtk_Node::DrawingType:
                {
                    WriteDrawing( inNode );
                    break;
                }
                case Dtk_Node::MeshType:
                {
                    WriteMesh( inNode, outIds );
                    break;
                }
                case Dtk_Node::AxisSystemType:
                {
                    WriteAxisSystem( inNode );
                    break;
                }
                case Dtk_Node::KinematicsType:
                {
                    WriteKinematics( inNode );
                    break;
                }
                case Dtk_Node::LayerInfosSetType:
                {
                    WriteLayerFilter( inNode );
                    break;
                }
                case Dtk_Node::ColorInfosSetType:
                {
                    WriteColorPalette(inNode);
                    break;
                }
                case Dtk_Node::MetaDataType:
                {
                    WriteMetaData( inNode );
                    break;
                }
                case Dtk_Node::CameraType:
                {
                    WriteCamera( inNode );
                    break;
                }
                case Dtk_Node::ModelDisplayType:
                {
                    WriteModelDisplay( inNode );
                    break;
                }
                case Dtk_Node::ConstraintType:
                {
                    WriteConstraints( inNode );
                    break;
                }
                default:
                {
                    break;
                }
            }
        }
    
        //Treat recursive TreeNode for Visible and Construction node
        // NodeBlankedStatus : -1 = undefined, 0 = Visible, 1=Invisible, 2=Construction Geometry
        //remove this test if you want to treat invisible entities
        if( nodeBlankedStatus != 1 || nodeType == Dtk_Node::BodyType )
            //invisible body can have visible children
        {
    
            Dtk_Size_t i, numChildren;
            numChildren = inNode->GetNumChildren();
            for( i = 0; i < numChildren; i++ )
            {
                WriteNode( inNode->GetChild( i ), outIds );
            }
        }
    
    
        // Write Feature associated if you want to handle it
        WriteFeature( inNode );
    
    
        if (m_fileWriter)
        {
            m_fileWriter->EndNode();
        }
    
        return dtkNoError;
    }


    int WriteProcessBase::GetTesselationStatus()
    {
        return m_tessStatus;
    }
    
    DtkErrorStatus WriteProcessBase::InitTesselation()
    {
        DtkErrorStatus tesselationStatus = dtkErrorAllocation;
    
            //Initialization of tessellation library (used for PDF)
            tesselationStatus = (DtkErrorStatus)tess_InitTesselation("tess_tmp", 0.05);
    
            //Deactivating linear and angular tolerance => Angular tolerance of 10 degrees will be used
            tess_disableTolLinear(DTK_TRUE);
            tess_disableTolAngular(DTK_TRUE);
    
            //You can also use your own tessellation parameters :
            /*
            double LinearTol = 0.1;
            tess_set_linear(LinearTol);
            double AngularTol = 0.175; // angle in radian
            tess_set_angular(AngularTol); */
    
            if (tesselationStatus != dtkNoError)
            {
                std::cout << "Warning : Tesselation failed to start : " << dtkTypeError(tesselationStatus).c_str() << std::endl;
            }
        
        m_tessStatus = tesselationStatus;
        return tesselationStatus;
    }
    
    void WriteProcessBase::EndTesselation()
    {
         tess_EndTesselation();
    }

}
    